<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>Java多线程学习笔记 | Yeding's Blog</title><meta name="keywords" content="Hexo，博文"><meta name="author" content="叶町"><meta name="copyright" content="叶町"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="Java多线程学习记录">
<meta property="og:type" content="article">
<meta property="og:title" content="Java多线程学习笔记">
<meta property="og:url" content="https://www.yeding.top/post/08fb3a6a9e16/index.html">
<meta property="og:site_name" content="Yeding&#39;s Blog">
<meta property="og:description" content="Java多线程学习记录">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/5f6496f6160a154a674aaf48.jpg">
<meta property="article:published_time" content="2020-10-26T18:39:32.000Z">
<meta property="article:modified_time" content="2020-11-14T00:12:56.000Z">
<meta property="article:author" content="叶町">
<meta property="article:tag" content="Java">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/5f6496f6160a154a674aaf48.jpg"><link rel="shortcut icon" href="/img/webicon.jpg"><link rel="canonical" href="https://www.yeding.top/post/08fb3a6a9e16/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><meta name="msvalidate.01" content="278990AE0B56A7C826F87D7039A44A00"/><meta name="baidu-site-verification" content="code-c3G2TgtwfW"/><meta name="google-site-verification" content="_ae3149Ou1MfIkiKfOmwwj3AqiMp6ahEt9MT8YTVmtY"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":false,"languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#121212","position":"bottom-left"},
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: true,
  islazyload: true,
  isAnchor: true
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'Java多线程学习笔记',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2020-11-14 08:12:56'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="https://testingcf.jsdelivr.net/gh/Lost-Swords/ImageLibrary/css/index.min.css"><link rel="stylesheet" href="//at.alicdn.com/t/font_2829204_gazvsq5039v.css"><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/atom.xml" title="Yeding's Blog" type="application/atom+xml">
</head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src= "/image/loading.gif" data-lazy-src="https://s1.ax1x.com/2020/09/13/w0t8qe.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">10</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">12</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">4</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/5f6496f6160a154a674aaf48.jpg')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">Yeding's Blog</a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">Java多线程学习笔记</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2020-10-26T18:39:32.000Z" title="发表于 2020-10-27 02:39:32">2020-10-27</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2020-11-14T00:12:56.000Z" title="更新于 2020-11-14 08:12:56">2020-11-14</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E6%8A%80%E6%9C%AF/">技术</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">8.3k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>25分钟</span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h1 id="1基本概念"><a class="markdownIt-Anchor" href="#1基本概念"></a> 1.基本概念</h1>
<p>程序：</p>
<blockquote>
<p>用某种编程语言(java、python等)编写，能够完成一定任务或者功能的代码集合,是指令和数据的有序集合，是<strong>一段静态代码</strong>。</p>
</blockquote>
<p>进程：</p>
<blockquote>
<p>进程是程序的一次执行过程，是系统运行程序的基本单位</p>
</blockquote>
<p>线程：</p>
<blockquote>
<p>线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。</p>
</blockquote>
<h3 id="并发和并行的区别"><a class="markdownIt-Anchor" href="#并发和并行的区别"></a> 并发和并行的区别：</h3>
<ul>
<li><strong>并发：</strong> 同一时间段，多个任务都在执行 (单位时间内不一定同时执行)；</li>
<li><strong>并行：</strong> 单位时间内，多个任务同时执行。</li>
</ul>
<h3 id="为什么使用多线程实现并发"><a class="markdownIt-Anchor" href="#为什么使用多线程实现并发"></a> 为什么使用多线程实现并发：</h3>
<p>多进程方式确实可以实现并发，但使用多线程，有以下几个好处：</p>
<ul>
<li>进程间的通信比较复杂，而线程间的通信比较简单，通常情况下，我们需要使用共享资源，这些资源在线程间的通信比较容易。</li>
<li>进程是重量级的，而线程是轻量级的，故多线程方式的系统开销更小。</li>
</ul>
<h3 id="进程与线程的区别"><a class="markdownIt-Anchor" href="#进程与线程的区别"></a> 进程与线程的区别：</h3>
<p>进程是一个独立的运行环境，而线程是在进程中执行的一个任务。他们两个本质的区别是<strong>是否单独占有内存地址空间及其它系统资源（比如I/O）</strong>：</p>
<ul>
<li>
<p>进程单独占有一定的内存地址空间，所以进程间存在内存隔离，数据是分开的，数据共享复杂但是同步简单，各个进程之间互不干扰；而线程共享所属进程占有的内存地址空间和资源，数据共享简单，但是同步复杂。</p>
</li>
<li>
<p>进程单独占有一定的内存地址空间，一个进程出现问题不会影响其他进程，不影响主程序的稳定性，可靠性高；一个线程崩溃可能影响整个程序的稳定性，可靠性较低。</p>
</li>
<li>
<p>进程单独占有一定的内存地址空间，进程的创建和销毁不仅需要保存寄存器和栈信息，还需要资源的分配回收以及页调度，开销较大；线程只需要保存寄存器和栈信息，开销较小。</p>
</li>
</ul>
<p>另外一个重要区别是，<strong>进程是操作系统进行资源分配的基本单位，而线程是操作系统进行调度的基本单位</strong>，即CPU分配时间的单位 。</p>
<p><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/JVM_Runtime_DataFiled.png" alt="" /><br />
从上图可以看出：一个进程中可以有多个线程，多个线程共享进程的<strong>堆</strong>和<strong>方法区 (JDK1.8 之后的元空间)**资源，但是每个线程有自己的</strong>程序计数器**、<strong>虚拟机栈</strong> 和 <strong>本地方法栈</strong>。</p>
<p><strong>总结：</strong> <strong>线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的，而各线程则不一定，因为同一进程中的线程极有可能会相互影响。线程执行开销小，但不利于资源的管理和保护；而进程正相反。</strong></p>
<h3 id="java线程状态切换"><a class="markdownIt-Anchor" href="#java线程状态切换"></a> Java线程状态切换</h3>
<p>Java线程模型定义了 6 种状态，在任意一个时间点，一个线程有且只有其中一个状态：</p>
<ul>
<li><code>新建（New）</code>：新建的Thread，尚未开始。</li>
<li><code>运行（Runable）</code>：包含操作系统线程状态中的Running、Ready，也就是处于正在执行或正在等待CPU分配时间的状态。</li>
<li><code>无限期等待（Waiting）</code>：处于这种状态的线程不会被分配CPU时间，等待其他线程唤醒。</li>
<li><code>限期等待（Timed Waiting）</code>：处于这种状态的线程不会被分配CPU时间，在一定时间后会由系统自动唤醒。</li>
<li><code>阻塞（Blocked）</code>：在等待获得排他锁。</li>
<li><code>结束（Terminated）</code>：已终止的线程。</li>
</ul>
<p><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/thread_status.jpg" alt="" /></p>
<h3 id="线程死锁"><a class="markdownIt-Anchor" href="#线程死锁"></a> 线程死锁</h3>
<p>线程死锁描述的是这样一种情况：多个线程同时被阻塞，它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞，因此程序不可能正常终止。</p>
<p>如下图所示，线程 A 持有资源 2，线程 B 持有资源 1，他们同时都想申请对方的资源，所以这两个线程就会互相等待而进入死锁状态。</p>
<p><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/2019-4%E6%AD%BB%E9%94%811.png" alt="线程死锁示意图 " /></p>
<p>产生死锁必须具备以下四个条件：</p>
<ol>
<li>互斥条件：该资源任意一个时刻只由一个线程占用。</li>
<li>请求与保持条件：一个进程因请求资源而阻塞时，对已获得的资源保持不放。</li>
<li>不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺，只有自己使用完毕后才释放资源。</li>
<li>循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。</li>
</ol>
<h1 id="2volatile"><a class="markdownIt-Anchor" href="#2volatile"></a> 2.Volatile</h1>
<h2 id="计算机内存模型"><a class="markdownIt-Anchor" href="#计算机内存模型"></a> 计算机内存模型</h2>
<p>计算机在执行程序时，每条指令都是在CPU中执行的，而执行指令过程中，势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存（物理内存）当中的，这时就存在一个问题，由于CPU执行速度很快，而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多，因此如果任何时候对数据的操作都要通过和内存的交互来进行，会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。<strong>当程序在运行过程中，会将运算需要的数据从主存复制一份到CPU的高速缓存当中，那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据，当运算结束之后，再将高速缓存中的数据刷新到主存当中</strong>。举个简单的例子，比如下面的这段代码：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">i = i + 1;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>当线程执行这个语句时，会先从主存当中读取<code>i</code>的值，然后复制一份到高速缓存当中，然后 CPU 执行指令对<code>i</code>进行加1操作，然后将数据写入高速缓存，最后将高速缓存中<code>i</code>最新的值刷新到主存当中。</p>
</blockquote>
<p>这个代码在单线程中运行是没有任何问题的，但是在多线程中运行就会有问题了。在多核 CPU 中，每条线程可能运行于不同的 CPU 中，因此 <strong>每个线程运行时有自己的高速缓存</strong>（对单核CPU来说，其实也会出现这种问题，只不过是以线程调度的形式来分别执行的）。比如同时有两个线程执行这段代码，假如初始时<code>i</code>的值为<code>0</code>，那么我们希望两个线程执行完之后i的值变为2。但是事实会是这样吗？</p>
<p>可能出现这种情况：初始时，<strong>两个线程分别读取<code>i</code>的值存入各自所在的 CPU 的高速缓存当中，然后 线程1 进行加1操作，然后把<code>i</code>的最新值1写入到内存。此时线程2的高速缓存当中<code>i</code>的值还是0，进行加1操作之后，<code>i</code>的值为1，然后线程2把i的值写入内存。最终结果<code>i</code>的值是1，而不是2。这就是著名的缓存一致性问题</strong>。通常称这种被多个线程访问的变量为共享变量。</p>
<p>为了解决缓存不一致性问题，通常来说有以下两种解决方法：</p>
<ul>
<li>通过在总线加<code>LOCK#</code>锁的方式</li>
<li>通过 <strong>缓存一致性协议</strong></li>
</ul>
<blockquote>
<p>这两种方式都是硬件层面上提供的方式。</p>
</blockquote>
<p>在早期的 CPU 当中，是通过在总线上加<code>LOCK#</code>锁的形式来解决缓存不一致的问题。因为 CPU 和其他部件进行通信都是通过总线来进行的，如果对总线加LOCK#锁的话，也就是说阻塞了其他 CPU 对其他部件访问（如内存），从而使得只能有一个 CPU 能使用这个变量的内存。比如上面例子中 如果一个线程在执行 <code>i = i +1</code>，如果在执行这段代码的过程中，在总线上发出了<code>LCOK#</code>锁的信号，那么只有等待这段代码完全执行完毕之后，其他CPU才能从变量i所在的内存读取变量，然后进行相应的操作。这样就解决了缓存不一致的问题。但是上面的方式会有一个问题，<strong>由于在锁住总线期间，其他CPU无法访问内存，导致效率低下</strong>。</p>
<p>所以就出现了缓存一致性协议。最出名的就是 Intel 的<code>MESI协议</code>，<code>MESI协议</code>保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是：<strong>当CPU写数据时，如果发现操作的变量是共享变量，即在其他CPU中也存在该变量的副本，会发出信号通知其他CPU将该变量的缓存行置为无效状态，因此当其他CPU需要读取这个变量时，发现自己缓存中缓存该变量的缓存行是无效的，那么它就会从内存重新读取</strong>。</p>
<p><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/volatile_1.jpg" alt="" /></p>
<h2 id="java内存模型"><a class="markdownIt-Anchor" href="#java内存模型"></a> Java内存模型</h2>
<p>在Java虚拟机规范中试图定义一种Java内存模型（<code>Java Memory Model，JMM</code>）来屏蔽各个硬件平台和操作系统的内存访问差异，以实现让Java程序在各种平台下都能达到一致的内存访问效果。那么Java内存模型规定了程序中变量的访问规则，往大一点说是定义了程序执行的次序。<strong>注意，为了获得较好的执行性能，Java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度，也没有限制编译器对指令进行重排序。也就是说，在java内存模型中，也会存在缓存一致性问题和指令重排序的问题</strong>。</p>
<p><strong>Java内存模型规定所有的变量都是存在主存当中（类似于前面说的物理内存），每个线程都有自己的工作内存（类似于前面的高速缓存）。线程对变量的所有操作都必须在工作内存中进行，而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存</strong>。</p>
<p>在Java中，执行下面这个语句：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">i  = 10;</span><br></pre></td></tr></table></figure>
<p>执行线程必须先在自己的工作线程中对变量<code>i</code>所在的缓存行进行赋值操作，然后再写入主存当中。而不是直接将数值<code>10</code>写入主存当中。那么Java语言本身对 原子性、可见性以及有序性提供了哪些保证呢？</p>
<h3 id="原子性"><a class="markdownIt-Anchor" href="#原子性"></a> 原子性</h3>
<blockquote>
<p>即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断，要么就都不执行。</p>
</blockquote>
<p><strong>在Java中，对基本数据类型的变量的读取和赋值操作是原子性操作，即这些操作是不可被中断的，要么执行，要么不执行</strong>。上面一句话虽然看起来简单，但是理解起来并不是那么容易。看下面一个例子，请分析以下哪些操作是原子性操作：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">x = 10;        //语句1</span><br><span class="line">y = x;         //语句2</span><br><span class="line">x++;           //语句3</span><br><span class="line">x = x + 1;     //语句4</span><br></pre></td></tr></table></figure>
<p>咋一看，有些朋友可能会说上面的4个语句中的操作都是原子性操作。<strong>其实只有<code>语句1</code>是原子性操作，其他三个语句都不是原子性操作</strong>。</p>
<ul>
<li><code>语句1</code>是直接将数值<code>10</code>赋值给<code>x</code>，也就是说线程执行这个语句的会直接将数值<code>10</code>写入到工作内存中。</li>
<li><code>语句2</code>实际上包含2个操作，它先要去读取<code>x</code>的值，再将<code>x</code>的值写入工作内存，虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作，但是合起来就不是原子性操作了。</li>
<li>同样的，<code>x++</code>和 <code>x = x+1</code>包括3个操作：读取<code>x</code>的值，进行加<code>1</code>操作，写入新的值。</li>
</ul>
<p>也就是说，<strong>只有简单的读取、赋值（而且必须是将数字赋值给某个变量，变量之间的相互赋值不是原子操作）才是原子操作</strong>。不过这里有一点需要注意：<strong>在32位平台下，对64位数据的读取和赋值是需要通过两个操作来完成的，不能保证其原子性。但是好像在最新的JDK中，JVM已经保证对64位数据的读取和赋值也是原子性操作了</strong>。</p>
<p>从上面可以看出，Java内存模型只保证了基本读取和赋值是原子性操作，如果要实现更大范围操作的原子性，可以通过<code>synchronize</code>d和<code>Lock</code>来实现。由于<code>synchronized</code>和<code>Lock</code>能够保证任一时刻只有一个线程执行该代码块，那么自然就不存在原子性问题了，从而保证了原子性。</p>
<h3 id="可见性"><a class="markdownIt-Anchor" href="#可见性"></a> 可见性</h3>
<blockquote>
<p>可见性是指当多个线程访问同一个变量时，一个线程修改了这个变量的值，其他线程能够立即看得到修改的值。</p>
</blockquote>
<p>对于可见性，Java提供了<code>volatile</code>关键字来保证可见性。<strong>当一个共享变量被<code>volatile</code>修饰时，它会保证修改的值会立即被更新到主存，当有其他线程需要读取时，它会去内存中读取新值</strong>。而普通的共享变量不能保证可见性，因为普通共享变量被修改之后，什么时候被写入主存是不确定的，当其他线程去读取时，此时内存中可能还是原来的旧值，因此无法保证可见性。</p>
<p>另外，通过<code>synchronized</code>和<code>Lock</code>也能够保证可见性，<code>synchronized</code>和<code>Lock</code>能保证同一时刻只有一个线程获取锁然后执行同步代码，并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。</p>
<h3 id="有序性"><a class="markdownIt-Anchor" href="#有序性"></a> 有序性</h3>
<blockquote>
<p>即程序执行的顺序按照代码的先后顺序执行。</p>
</blockquote>
<blockquote>
<p>指令重排序，一般来说，处理器为了提高程序运行效率，可能会对输入代码进行优化，它不保证程序中各个语句的执行先后顺序同代码中的顺序一致，但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。</p>
</blockquote>
<p><strong>处理器在进行重排序时是会考虑指令之间的数据依赖性，如果一个指令Instruction 2必须用到Instruction 1的结果，那么处理器会保证Instruction 1会在Instruction 2之前执行</strong>。</p>
<p>在Java内存模型中，允许编译器和处理器对指令进行重排序，但是重排序过程不会影响到单线程程序的执行，却会影响到多线程并发执行的正确性。</p>
<p>在Java里面，可以通过<code>volatile</code>关键字来保证一定的“有序性”（具体原理在下一节讲述）。另外可以通过<code>synchronized</code>和<code>Lock</code>来保证有序性，很显然，<code>synchronized</code>和<code>Lock</code>保证每个时刻是有一个线程执行同步代码，相当于是让线程顺序执行同步代码，自然就保证了有序性。</p>
<p>另外，Java内存模型具备一些先天的“有序性”，即不需要通过任何手段就能够得到保证的有序性，这个通常也称为 <code>happens-before</code> 原则，<strong>若线程 A 和线程 B 满足 happens-before 关系，则线程 A 执行操作的结果对线程 B 是可见的</strong>。如果两个操作的执行次序无法从<code>happens-before</code>原则推导出来，那么它们就不能保证它们的有序性，虚拟机可以随意地对它们进行重排序。</p>
<p>下面就来具体介绍下<code>happens-before</code>原则（先行发生原则）：</p>
<ul>
<li><strong>程序次序规则</strong>：一个线程内，按照代码顺序，书写在前面的操作先行发生于书写在后面的操作</li>
<li><strong>锁定规则</strong>：一个unLock操作先行发生于后面对同一个锁额lock操作</li>
<li><strong>volatile变量规则</strong>：对一个变量的写操作先行发生于后面对这个变量的读操作</li>
<li><strong>传递规则</strong>：如果操作A先行发生于操作B，而操作B又先行发生于操作C，则可以得出操作A先行发生于操作C</li>
<li><strong>线程启动规则</strong>：Thread对象的start()方法先行发生于此线程的每个一个动作</li>
<li><strong>线程中断规则</strong>：对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生</li>
<li><strong>线程终结规则</strong>：线程中所有的操作都先行发生于线程的终止检测，我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行</li>
<li><strong>对象终结规则</strong>：一个对象的初始化完成先行发生于他的finalize()方法的开始</li>
</ul>
<p>对于程序次序规则来说，我的理解就是一段程序代码的执行在单个线程中看起来是有序的。注意，虽然这条规则中提到“书写在前面的操作先行发生于书写在后面的操作”，这个应该是程序看起来执行的顺序是按照代码顺序执行的，因为虚拟机可能会对程序代码进行指令重排序。虽然进行重排序，但是最终执行的结果是与程序顺序执行的结果一致的，它只会对不存在数据依赖性的指令进行重排序。因此，在单个线程中，程序执行看起来是有序执行的，这一点要注意理解。事实上，这个规则是用来保证程序在单线程中执行结果的正确性，但无法保证程序在多线程中执行的正确性。</p>
<p>第二条规则也比较容易理解，也就是说无论在单线程中还是多线程中，同一个锁如果出于被锁定的状态，那么必须先对锁进行了释放操作，后面才能继续进行lock操作。</p>
<p>第三条规则是一条比较重要的规则，也是后文将要重点讲述的内容。<strong>直观地解释就是，如果一个线程先去写一个变量，然后一个线程去进行读取，那么写入操作肯定会先行发生于读操作</strong>。</p>
<p>第四条规则实际上就是体现<code>happens-before</code>原则具备传递性。</p>
<h2 id="深入剖析volatile关键字"><a class="markdownIt-Anchor" href="#深入剖析volatile关键字"></a> 深入剖析Volatile关键字</h2>
<h3 id="volatile-的语义"><a class="markdownIt-Anchor" href="#volatile-的语义"></a> Volatile 的语义</h3>
<p>一旦一个共享变量（类的成员变量、类的静态成员变量）被<code>volatile</code>修饰之后，那么就具备了两层语义：</p>
<ul>
<li>保证了不同线程对这个变量进行操作时的可见性，即一个线程修改了某个变量的值，这新值对其他线程来说是立即可见的</li>
<li>禁止进行指令重排序</li>
</ul>
<p>先看一段代码，假如线程1先执行，线程2后执行：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">//线程1</span><br><span class="line">boolean stop = false;</span><br><span class="line">while(!stop)&#123;</span><br><span class="line">    doSomething();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">//线程2</span><br><span class="line">stop = true;</span><br></pre></td></tr></table></figure>
<p>这段代码是很典型的一段代码，很多人在中断线程时可能都会采用这种标记办法。但是事实上，这段代码会完全运行正确么？即一定会将线程中断么？不一定，也许在大多数时候，这个代码能够把线程中断，但是也有可能会导致无法中断线程（虽然这个可能性很小，但是只要一旦发生这种情况就会造成死循环了）。</p>
<p>下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过，每个线程在运行过程中都有自己的工作内存，那么<code>线程1</code>在运行的时候，会将<code>stop</code>变量的值拷贝一份放在自己的工作内存当中。</p>
<p>那么当<code>线程2</code>更改了<code>stop</code>变量的值之后，但是还没来得及写入主存当中，<code>线程2</code>转去做其他事情了，那么<code>线程1</code>由于不知道<code>线程2</code>对<code>stop</code>变量的更改，因此还会一直循环下去。但是用<code>volatile</code>修饰之后就变得不一样了：</p>
<ul>
<li>使用<code>volatile</code>关键字会强制将修改的值立即写入主存；</li>
<li>使用<code>volatile</code>关键字的话，当<code>线程2</code>进行修改时，会导致<code>线程1</code>的工作内存中缓存变量<code>stop</code>的缓存行无效（<em>反映到硬件层的话，就是CPU的L1或者L2缓存中对应的缓存行无效</em>）；</li>
<li>由于<code>线程1</code>的工作内存中缓存变量<code>stop</code>的缓存行无效，所以<code>线程1</code>再次读取变量<code>stop</code>的值时会去主存读取。</li>
<li>那么在<code>线程2</code>修改<code>stop</code>值时（当然这里包括2个操作，修改线程2工作内存中的值，然后将修改后的值写入内存），会使得<code>线程1</code>的工作内存中缓存变量<code>stop</code>的缓存行无效，然后<code>线程1</code>读取时，发现自己的缓存行无效，它会等待缓存行对应的主存地址被更新之后，然后去对应的主存读取最新的值。</li>
</ul>
<p>那么线程1读取到的就是最新的正确的值。</p>
<h3 id="volatile与原子性"><a class="markdownIt-Anchor" href="#volatile与原子性"></a> Volatile与原子性</h3>
<p>从上面知道<code>volatile</code>关键字保证了操作的可见性，但是<code>volatile</code>能保证对变量的操作是原子性吗？</p>
<p>下面看一个例子：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">public class Test &#123;</span><br><span class="line">    public volatile int inc = 0;</span><br><span class="line"></span><br><span class="line">    public void increase() &#123;</span><br><span class="line">        inc++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) &#123;</span><br><span class="line">        final Test test = new Test();</span><br><span class="line">        for(int i=0;i&lt;10;i++)&#123;</span><br><span class="line">            new Thread()&#123;</span><br><span class="line">                public void run() &#123;</span><br><span class="line">                    for(int j=0;j&lt;1000;j++)</span><br><span class="line">                        test.increase();</span><br><span class="line">                &#125;;</span><br><span class="line">            &#125;.start();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        while(Thread.activeCount()&gt;1)  //保证前面的线程都执行完</span><br><span class="line">            Thread.yield();</span><br><span class="line">        System.out.println(test.inc);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>大家想一下这段程序的输出结果是多少？<strong>也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致，都是一个小于10000的数字</strong>。可能有的朋友就会有疑问，不对啊，上面是对变量<code>inc</code>进行自增操作，由于<code>volatile</code>保证了可见性，那么在每个线程中对<code>inc</code>自增完之后，在其他线程中都能看到修改后的值啊，所以有10个线程分别进行了1000次操作，那么最终<code>inc</code>的值应该是<code>1000*10=10000</code>。</p>
<p><strong>这里面就有一个误区了，volatile关键字能保证可见性没有错，但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值，但是volatile没办法保证对变量的操作的原子性</strong>。</p>
<p>在前面已经提到过，自增操作是不具备原子性的，它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行，就有可能导致下面这种情况出现：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">假如某个时刻变量inc的值为10，</span><br><span class="line"></span><br><span class="line">线程1对变量进行自增操作，线程1先读取了变量inc的原始值，然后线程1被阻塞了；</span><br><span class="line"></span><br><span class="line">然后线程2对变量进行自增操作，线程2也去读取变量inc的原始值，由于线程1只是对变量inc进行读取操作，而没有对变量进行修改操作，所以不会导致线程2的工作内存中缓存变量inc的缓存行无效，所以线程2会直接去主存读取inc的值，发现inc的值时10，然后进行加1操作，并把11写入工作内存，最后写入主存。</span><br><span class="line"></span><br><span class="line">然后线程1接着进行加1操作，由于已经读取了inc的值，注意此时在线程1的工作内存中inc的值仍然为10，所以线程1对inc进行加1操作后inc的值为11，然后将11写入工作内存，最后写入主存。</span><br><span class="line"></span><br><span class="line">那么两个线程分别进行了一次自增操作后，inc只增加了1。</span><br></pre></td></tr></table></figure>
<p>解释到这里，可能有朋友会有疑问，不对啊，前面不是保证一个变量在修改volatile变量时，会让缓存行无效吗？然后其他线程去读就会读到新的值，对，这个没错。这个就是上面的<code>happens-before</code>规则中的<code>volatile</code>变量规则，但是要注意，<strong>线程1对变量进行读取操作之后，被阻塞了的话，并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的，但是线程1没有进行修改，所以线程2根本就不会看到修改的值</strong>。</p>
<p><strong>根源就在这里，自增操作不是原子性操作，而且volatile也无法保证对变量的任何操作都是原子性的</strong>。解决的方法也就是对提供原子性的自增操作即可。</p>
<p>在<code>Java 1.5</code>的<code>java.util.concurrent.atomic</code>包下提供了一些原子操作类，即对基本数据类型的 自增（加1操作），自减（减1操作）、以及加法操作（加一个数），减法操作（减一个数）进行了封装，保证这些操作是原子性操作。<code>atomic</code>是利用CAS来实现原子性操作的（<code>Compare And Swap</code>），CAS实际上是利用处理器提供的 <code>CMPXCHG</code> 指令实现的，而处理器执行 <code>CMPXCHG</code> 指令是一个原子性操作。</p>
<h3 id="volatile与有序性"><a class="markdownIt-Anchor" href="#volatile与有序性"></a> Volatile与有序性</h3>
<p>在前面提到<code>volatile</code>关键字能禁止指令重排序，所以<code>volatile</code>能在一定程度上保证有序性。<code>volatile</code>关键字禁止指令重排序有两层意思：</p>
<ul>
<li>当程序执行到<code>volatile</code>变量的读操作或者写操作时，在其前面的操作的更改肯定全部已经进行，且结果已经对后面的操作可见，在其后面的操作肯定还没有进行；</li>
<li><strong>在进行指令优化时，不能将在对<code>volatile</code>变量访问的语句放在其后面执行，也不能把<code>volatile</code>变量后面的语句放到其前面执行</strong>。</li>
</ul>
<p>可能上面说的比较绕，举个简单的例子：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">//x、y为非volatile变量</span><br><span class="line">//flag为volatile变量</span><br><span class="line"></span><br><span class="line">x = 2;        //语句1</span><br><span class="line">y = 0;        //语句2</span><br><span class="line">flag = true;  //语句3</span><br><span class="line">x = 4;         //语句4</span><br><span class="line">y = -1;       //语句5</span><br></pre></td></tr></table></figure>
<p>由于flag变量为<code>volatile</code>变量，那么在进行指令重排序的过程的时候，不会将<code>语句3</code>放到<code>语句1</code>、<code>语句2</code>前面，也不会讲<code>语句3</code>放到<code>语句4</code>、<code>语句5</code>后面。但是要注意<code>语句1</code>和<code>语句2</code>的顺序、<code>语句4</code>和<code>语句5</code>的顺序是不作任何保证的。</p>
<p>并且<code>volatile</code>关键字能保证，执行到<code>语句3</code>时<code>，语句1</code>和<code>语句2</code>必定是执行完毕了的，且<code>语句1</code>和<code>语句2</code>的执行结果对<code>语句3</code>、<code>语句4</code>、<code>语句5</code>是可见的。</p>
<h3 id="volatile的原理和实现机制"><a class="markdownIt-Anchor" href="#volatile的原理和实现机制"></a> Volatile的原理和实现机制</h3>
<p>前面讲述了源于volatile关键字的一些使用，下面我们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。下面这段话摘自《深入理解Java虚拟机》：</p>
<blockquote>
<p>观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现，加入volatile关键字时，会多出一个lock前缀指令</p>
</blockquote>
<p>lock前缀指令实际上相当于一个 <strong>内存屏障</strong>（也成内存栅栏），内存屏障会提供3个功能：</p>
<ul>
<li>它 <strong>确保指令重排序时不会把其后面的指令排到内存屏障之前的位置，也不会把前面的指令排到内存屏障的后面</strong>；即在执行到内存屏障这句指令时，在它前面的操作已经全部完成；</li>
<li>它会 <strong>强制将对缓存的修改操作立即写入主存</strong>；</li>
<li><strong>如果是写操作，它会导致其他CPU中对应的缓存行无效</strong>。</li>
</ul>
<h1 id="3synchronized"><a class="markdownIt-Anchor" href="#3synchronized"></a> 3.Synchronized</h1>
<h2 id="基础"><a class="markdownIt-Anchor" href="#基础"></a> 基础</h2>
<p>在多线程并发编程中 <code>Synchronized</code> 一直是元老级角色，很多人都会称呼它为重量级锁，但是随着 <code>Java SE1.6</code> 对 <code>Synchronized</code> 进行了各种优化，引入了 <strong>偏向锁</strong> 和 <strong>轻量级锁</strong>。所以在 Java SE1.6 里锁一共有四种状态，<code>无锁状态</code>，<code>偏向锁状态</code>，<code>轻量级锁状态</code>和<code>重量级锁状态</code>，它会随着竞争情况逐渐升级。<strong>锁可以升级但不能降级</strong>，意味着偏向锁升级成轻量级锁后不能降级成偏向锁，但是偏向锁状态可以被重置为无锁状态（锁撤销）。这种锁升级却不能降级的策略，目的是为了提高获得锁和释放锁的效率。</p>
<table>
<thead>
<tr>
<th>锁状态</th>
<th>优点</th>
<th>缺点</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td>偏向锁</td>
<td>加锁、解锁无额外消耗，和非同步方式近似</td>
<td>如果竞争线程多，会有额外锁撤销的消耗</td>
<td>基本没有线程竞争的场景</td>
</tr>
<tr>
<td>轻量级锁</td>
<td>竞争线程不会阻塞，使用自旋等待</td>
<td>如果长时间不能获取锁，会消耗CPU</td>
<td>少量线程竞争，且线程持有锁时间不长</td>
</tr>
<tr>
<td>重量级锁</td>
<td>竞争线程被阻塞，减少CPU空转</td>
<td>线程阻塞，响应时间长</td>
<td>很多线程竞争，锁持有时间长</td>
</tr>
</tbody>
</table>
<p>Java中的每一个对象都可以作为锁。</p>
<ul>
<li>对于同步方法，锁是当前实例对象。</li>
<li>对于静态同步方法，锁是当前对象的Class对象。</li>
<li>对于同步方法块，锁是<code>Synchonized</code>括号里配置的对象。</li>
</ul>
<p>当一个线程试图访问同步代码块时，它首先必须得到锁，退出或抛出异常时必须释放锁。</p>
<h2 id="锁的升级"><a class="markdownIt-Anchor" href="#锁的升级"></a> 锁的升级</h2>
<p>目前锁一共有4种状态，级别从低到高依次是：无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级。</p>
<h3 id="偏向锁"><a class="markdownIt-Anchor" href="#偏向锁"></a> 偏向锁</h3>
<p>大多数情况下，锁不仅不存在多线程竞争，而且总是由同一线程多次获得。偏向锁是为了在只有一个线程执行同步块时提高性能。</p>
<p><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/biased_lock.svg" alt="biased_lock" /></p>
<h3 id="轻量级锁"><a class="markdownIt-Anchor" href="#轻量级锁"></a> 轻量级锁</h3>
<p><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/light_lock.svg" alt="img" /></p>
<p>这里解释下其中几个重要的步骤：</p>
<ul>
<li>复制 Mark Word 到锁记录：拷贝对象头中的 Mark Word 到锁记录中。</li>
<li>更新 Mark Word 指针：拷贝成功后，虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 指针，并将 Lock Record 里的 owner 指针指向对象的 Mark Word。</li>
</ul>
<h3 id="重量级锁"><a class="markdownIt-Anchor" href="#重量级锁"></a> 重量级锁</h3>
<p>在重量级锁的状态下， JVM 基于进入和退出 <code>Monitor</code> 对象来实现方法同步和代码块同步，<code>Monitor</code> 的引用存储在对象头中。</p>
<p><code>Monitor</code> 本身是依赖与操作系统的互斥锁（mutex lock）实现的。由于 JVM 线程是映射到操作系统的原生线程之上的，如果要阻塞或唤醒一条线程，都需要操作系统来帮忙完成，这就需要从用户态转换到核心态中，因此这种转换需要耗费很多的 CPU 时间。</p>
<p><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/monitor_lock.svg" alt="img" /></p>
<h2 id="锁粗化"><a class="markdownIt-Anchor" href="#锁粗化"></a> 锁粗化</h2>
<p>同步块的作用范围应该尽可能小，仅在共享数据的实际作用域中才进行同步，这样做的目的是为了使需要同步的操作数量尽可能缩小，缩短阻塞时间，如果存在锁竞争，那么等待锁的线程也能尽快拿到锁。</p>
<p>但是加锁解锁也需要消耗资源，如果存在一系列的连续加锁解锁操作，可能会导致不必要的性能损耗。 <strong>锁粗化就是 JVM 将多个连续的加锁、解锁操作连接在一起</strong>，扩展成一个范围更大的锁，避免频繁的加锁解锁操作。</p>
<h2 id="锁消除"><a class="markdownIt-Anchor" href="#锁消除"></a> 锁消除</h2>
<p>Java 虚拟机在 JIT 编译时(可以简单理解为当某段代码即将第一次被执行时进行编译，又称即时编译)，通过对运行上下文的扫描，经过逃逸分析，<strong>去除不可能存在共享资源竞争的锁，通过这种方式消除没有必要的锁</strong>，可以节省毫无意义的请求锁时间</p>
<h2 id="synchronized-vs-reentrantlock"><a class="markdownIt-Anchor" href="#synchronized-vs-reentrantlock"></a> Synchronized vs ReentrantLock</h2>
<ol>
<li>synchronized 是 Java 关键字，ReentrantLock 是基于 AQS 的 API 层面的互斥锁</li>
<li>ReentrantLock 设置等待超时时间</li>
<li>ReentrantLock 可进行公平锁与非公平锁设置</li>
<li>ReentrantLock 可绑定多个 Condition</li>
<li>synchronized 不需要手动释放锁</li>
<li>synchronized 可以修饰方法、代码块</li>
</ol>
<p><strong>待续~</strong></p>
<p><strong>参考资料</strong></p>
<hr />
<p><a target="_blank" rel="noopener" href="http://concurrent.redspider.group/RedSpider.html">深入浅出Java多线程</a></p>
<p><a target="_blank" rel="noopener" href="https://hadyang.github.io/interview/">Java计算机知识汇总</a></p>
<p><a target="_blank" rel="noopener" href="https://snailclimb.gitee.io/javaguide/#/">JavaGuide</a></p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="https://www.yeding.top">叶町</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://www.yeding.top/post/08fb3a6a9e16/">https://www.yeding.top/post/08fb3a6a9e16/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://www.yeding.top" target="_blank">Yeding's Blog</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/Java/">Java</a></div><div class="post_share"><div class="social-share" data-image="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/5f6496f6160a154a674aaf48.jpg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/social-share.js/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/gh/overtrue/share.js@master/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/post/f3d732a55ffc/"><img class="prev-cover" src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/5f6763fd160a154a6709d5e6.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">MYSQL数据库事务和索引学习</div></div></a></div><div class="next-post pull-right"><a href="/post/50b39b8439ac/"><img class="next-cover" src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/85572728_p0.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">绿皮书N2语法错题汇总</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/post/dde04d069e3b/" title="JVM入门笔记"><img class="cover" src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/89478039_p0.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-09-18</div><div class="title">JVM入门笔记</div></div></a></div><div><a href="/post/5e47ee7383cc/" title="SpringBoot常用注解"><img class="cover" src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/86587640_p0.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-12-31</div><div class="title">SpringBoot常用注解</div></div></a></div></div></div><hr/><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="fas fa-comments fa-fw"></i><span> 评论</span></div></div><div class="comment-wrap"><div><div id="twikoo-wrap"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src= "/image/loading.gif" data-lazy-src="https://s1.ax1x.com/2020/09/13/w0t8qe.jpg" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">叶町</div><div class="author-info__description">不要去了解我，也不要用因果报应咒骂我</div></div><div class="card-info-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">10</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">12</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">4</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://space.bilibili.com/3169866"><i class="iconfont icon-bilibili-line"></i><span>关注我的B站账号</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://testingcf.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/weixinCode.jpg" target="_blank" title="微信"><i class="fab fa-weixin"></i></a><a class="social-icon" href="tencent://message/?uin=502818113&amp;Site=yedingBlog&amp;Menu=yes" target="_blank" title="QQ"><i class="fab fa-qq"></i></a><a class="social-icon" href="https://github.com/Lost-Swords" target="_blank" title="Github"><i class="fab fa-github"></i></a><a class="social-icon" href="mailto:kanaumachi@gmail.com" target="_blank" title="Email"><i class="fas fa-envelope"></i></a><a class="social-icon" href="/atom.xml" target="_blank" title=""><i class="fas fa-rss"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content"><div class="social-button"> <a style="display:block;margin:.2rem 0;background-color:var(--btn-bg);color:var(--btn-color);text-align:center;line-height:2.4;;transition:all .3s;position:relative;z-index:1;" class="button--animated" href="https://yeding708.lofter.com/" rel="external nofollow noreferrer" target="_blank"><i class="iconfont icon-lofter"></i><span>关注我的LOFTER</span></a> </div></div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#1%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5"><span class="toc-text"> 1.基本概念</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B9%B6%E5%8F%91%E5%92%8C%E5%B9%B6%E8%A1%8C%E7%9A%84%E5%8C%BA%E5%88%AB"><span class="toc-text"> 并发和并行的区别：</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%AE%9E%E7%8E%B0%E5%B9%B6%E5%8F%91"><span class="toc-text"> 为什么使用多线程实现并发：</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BF%9B%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%8C%BA%E5%88%AB"><span class="toc-text"> 进程与线程的区别：</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#java%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%88%87%E6%8D%A2"><span class="toc-text"> Java线程状态切换</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%BA%BF%E7%A8%8B%E6%AD%BB%E9%94%81"><span class="toc-text"> 线程死锁</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#2volatile"><span class="toc-text"> 2.Volatile</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B"><span class="toc-text"> 计算机内存模型</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B"><span class="toc-text"> Java内存模型</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8E%9F%E5%AD%90%E6%80%A7"><span class="toc-text"> 原子性</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8F%AF%E8%A7%81%E6%80%A7"><span class="toc-text"> 可见性</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%9C%89%E5%BA%8F%E6%80%A7"><span class="toc-text"> 有序性</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%B7%B1%E5%85%A5%E5%89%96%E6%9E%90volatile%E5%85%B3%E9%94%AE%E5%AD%97"><span class="toc-text"> 深入剖析Volatile关键字</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#volatile-%E7%9A%84%E8%AF%AD%E4%B9%89"><span class="toc-text"> Volatile 的语义</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#volatile%E4%B8%8E%E5%8E%9F%E5%AD%90%E6%80%A7"><span class="toc-text"> Volatile与原子性</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#volatile%E4%B8%8E%E6%9C%89%E5%BA%8F%E6%80%A7"><span class="toc-text"> Volatile与有序性</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#volatile%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E5%AE%9E%E7%8E%B0%E6%9C%BA%E5%88%B6"><span class="toc-text"> Volatile的原理和实现机制</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#3synchronized"><span class="toc-text"> 3.Synchronized</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%9F%BA%E7%A1%80"><span class="toc-text"> 基础</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%94%81%E7%9A%84%E5%8D%87%E7%BA%A7"><span class="toc-text"> 锁的升级</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%81%8F%E5%90%91%E9%94%81"><span class="toc-text"> 偏向锁</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81"><span class="toc-text"> 轻量级锁</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%87%8D%E9%87%8F%E7%BA%A7%E9%94%81"><span class="toc-text"> 重量级锁</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%94%81%E7%B2%97%E5%8C%96"><span class="toc-text"> 锁粗化</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%94%81%E6%B6%88%E9%99%A4"><span class="toc-text"> 锁消除</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#synchronized-vs-reentrantlock"><span class="toc-text"> Synchronized vs ReentrantLock</span></a></li></ol></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/post/a647efeab291/" title="【gal评】-交响乐之雨"><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/synphonic_haku.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="【gal评】-交响乐之雨"/></a><div class="content"><a class="title" href="/post/a647efeab291/" title="【gal评】-交响乐之雨">【gal评】-交响乐之雨</a><time datetime="2021-10-29T01:10:58.000Z" title="发表于 2021-10-29 09:10:58">2021-10-29</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/post/858ff2ba3ad9/" title="当我们在玩游戏时，我们在玩什么"><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/genshin.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="当我们在玩游戏时，我们在玩什么"/></a><div class="content"><a class="title" href="/post/858ff2ba3ad9/" title="当我们在玩游戏时，我们在玩什么">当我们在玩游戏时，我们在玩什么</a><time datetime="2021-05-19T15:37:10.000Z" title="发表于 2021-05-19 23:37:10">2021-05-19</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/post/114244095f3c/" title="你好，Hexo"><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/nier.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="你好，Hexo"/></a><div class="content"><a class="title" href="/post/114244095f3c/" title="你好，Hexo">你好，Hexo</a><time datetime="2021-04-16T09:21:58.000Z" title="发表于 2021-04-16 17:21:58">2021-04-16</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/post/709e1cd6d598/" title="日本语之森-N1语法（一）"><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/dog_food.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="日本语之森-N1语法（一）"/></a><div class="content"><a class="title" href="/post/709e1cd6d598/" title="日本语之森-N1语法（一）">日本语之森-N1语法（一）</a><time datetime="2021-04-16T04:29:17.000Z" title="发表于 2021-04-16 12:29:17">2021-04-16</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/post/8cecd96a62b8/" title="《标准日语语法》学习笔记——第一章绪论"><img src= "/image/loading.gif" data-lazy-src="https://gcore.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/86695978_p0.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="《标准日语语法》学习笔记——第一章绪论"/></a><div class="content"><a class="title" href="/post/8cecd96a62b8/" title="《标准日语语法》学习笔记——第一章绪论">《标准日语语法》学习笔记——第一章绪论</a><time datetime="2021-01-03T23:18:59.000Z" title="发表于 2021-01-04 07:18:59">2021-01-04</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2021 - 2022 By 叶町</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div><div class="footer_custom_text"><span>CDN BY <a target="_blank" rel="noopener" href="https://www.jsdelivr.com/">jsdelivr</a></span><br><div  style="display:flex;justify-content:center;align-items:center"><span>本网站由 </span><a href=https://www.upyun.com/?utm_source=lianmeng&utm_medium=referral><img height="30" src= "/image/loading.gif" data-lazy-src="https://testingcf.jsdelivr.net/gh/Lost-Swords/ImageLibrary/img/upyun_logo.png" alt="又拍云"></a><span>提供CDN加速</span></div></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="fas fa-comments"></i></a><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.js"></script><script src="https://cdn.jsdelivr.net/npm/instant.page@5/instantpage.min.js" type="module"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script>function panguFn () {
  if (typeof pangu === 'object') pangu.autoSpacingPage()
  else {
    getScript('https://cdn.jsdelivr.net/npm/pangu@4/dist/browser/pangu.min.js')
      .then(() => {
        pangu.autoSpacingPage()
      })
  }
}

function panguInit () {
  if (false){
    GLOBAL_CONFIG_SITE.isPost && panguFn()
  } else {
    panguFn()
  }
}

document.addEventListener('DOMContentLoaded', panguInit)</script><script src="/js/search/local-search.js"></script><div class="js-pjax"><script>(()=>{
  const init = () => {
    twikoo.init(Object.assign({
      el: '#twikoo-wrap',
      envId: 'hello-cloudbase-4g4zs9fdc78abf63',
      region: '',
      onCommentLoaded: function () {
        btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)'))
      }
    }, null))
  }

  const getCount = () => {
    const countELement = document.getElementById('twikoo-count')
    if(!countELement) return
    twikoo.getCommentsCount({
      envId: 'hello-cloudbase-4g4zs9fdc78abf63',
      region: '',
      urls: [window.location.pathname],
      includeReply: false
    }).then(function (res) {
      countELement.innerText = res[0].count
    }).catch(function (err) {
      console.error(err);
    });
  }

  const runFn = () => {
    init()
    
  }

  const loadTwikoo = () => {
    if (typeof twikoo === 'object') {
      setTimeout(runFn,0)
      return
    } 
    getScript('https://cdn.jsdelivr.net/npm/twikoo@1/dist/twikoo.all.min.js').then(runFn)
  }

  if ('Twikoo' === 'Twikoo' || !false) {
    if (false) btf.loadComment(document.getElementById('twikoo-wrap'), loadTwikoo)
    else loadTwikoo()
  } else {
    window.loadOtherComment = () => {
      loadTwikoo()
    }
  }
})()</script></div><script>window.addEventListener('load', () => {
  const changeContent = (content) => {
    if (content === '') return content

    content = content.replace(/<img.*?src="(.*?)"?[^\>]+>/ig, '[图片]') // replace image link
    content = content.replace(/<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[链接]') // replace url
    content = content.replace(/<pre><code>.*?<\/pre>/gi, '[代码]') // replace code
    content = content.replace(/<[^>]+>/g,"") // remove html tag

    if (content.length > 150) {
      content = content.substring(0,150) + '...'
    }
    return content
  }

  const getComment = () => {
    const runTwikoo = () => {
      twikoo.getRecentComments({
        envId: 'hello-cloudbase-4g4zs9fdc78abf63',
        region: '',
        pageSize: 6,
        includeReply: true
      }).then(function (res) {
        const twikooArray = res.map(e => {
          return {
            'content': changeContent(e.comment),
            'avatar': e.avatar,
            'nick': e.nick,
            'url': e.url + '#' + e.id,
            'date': new Date(e.created).toISOString()
          }
        })

        saveToLocal.set('twikoo-newest-comments', JSON.stringify(twikooArray), 10/(60*24))
        generateHtml(twikooArray)
      }).catch(function (err) {
        const $dom = document.querySelector('#card-newest-comments .aside-list')
        $dom.innerHTML= "无法获取评论，请确认相关配置是否正确"
      })
    }

    if (typeof twikoo === 'object') {
      runTwikoo()
    } else {
      getScript('https://cdn.jsdelivr.net/npm/twikoo@1/dist/twikoo.all.min.js').then(runTwikoo)
    }
  }

  const generateHtml = array => {
    let result = ''

    if (array.length) {
      for (let i = 0; i < array.length; i++) {
        result += '<div class=\'aside-list-item\'>'

        if (false) {
          const name = 'data-lazy-src'
          result += `<a href='${array[i].url}' class='thumbnail'><img ${name}='${array[i].avatar}' alt='${array[i].nick}'></a>`
        }
        
        result += `<div class='content'>
        <a class='comment' href='${array[i].url}' title='${array[i].content}'>${array[i].content}</a>
        <div class='name'><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div>
        </div></div>`
      }
    } else {
      result += '没有评论'
    }

    let $dom = document.querySelector('#card-newest-comments .aside-list')
    $dom.innerHTML= result
    window.lazyLoadInstance && window.lazyLoadInstance.update()
    window.pjax && window.pjax.refresh($dom)
  }

  const newestCommentInit = () => {
    if (document.querySelector('#card-newest-comments .aside-list')) {
      const data = saveToLocal.get('twikoo-newest-comments')
      if (data) {
        generateHtml(JSON.parse(data))
      } else {
        getComment()
      }
    }
  }

  newestCommentInit()
  document.addEventListener('pjax:complete', newestCommentInit)
})</script><script src="https://testingcf.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/autoload.js"></script><script defer="defer" id="ribbon" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/canvas-ribbon.min.js" size="150" alpha="0.6" zIndex="-1" mobile="false" data-click="true"></script><script src="https://cdn.jsdelivr.net/npm/pjax/pjax.min.js"></script><script>let pjaxSelectors = ["head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"]

var pjax = new Pjax({
  elements: 'a:not([target="_blank"])',
  selectors: pjaxSelectors,
  cacheBust: false,
  analytics: false,
  scrollRestoration: false
})

document.addEventListener('pjax:send', function () {

  // removeEventListener scroll 
  window.tocScrollFn && window.removeEventListener('scroll', window.tocScrollFn)
  window.scrollCollect && window.removeEventListener('scroll', scrollCollect)

  typeof preloader === 'object' && preloader.initLoading()
  document.getElementById('rightside').style.cssText = "opacity: ''; transform: ''"
  
  if (window.aplayers) {
    for (let i = 0; i < window.aplayers.length; i++) {
      if (!window.aplayers[i].options.fixed) {
        window.aplayers[i].destroy()
      }
    }
  }

  typeof typed === 'object' && typed.destroy()

  //reset readmode
  const $bodyClassList = document.body.classList
  $bodyClassList.contains('read-mode') && $bodyClassList.remove('read-mode')

})

document.addEventListener('pjax:complete', function () {
  window.refreshFn()

  document.querySelectorAll('script[data-pjax]').forEach(item => {
    const newScript = document.createElement('script')
    const content = item.text || item.textContent || item.innerHTML || ""
    Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value))
    newScript.appendChild(document.createTextNode(content))
    item.parentNode.replaceChild(newScript, item)
  })

  GLOBAL_CONFIG.islazyload && window.lazyLoadInstance.update()

  typeof chatBtnFn === 'function' && chatBtnFn()
  typeof panguInit === 'function' && panguInit()

  // google analytics
  typeof gtag === 'function' && gtag('config', '', {'page_path': window.location.pathname});

  // baidu analytics
  typeof _hmt === 'object' && _hmt.push(['_trackPageview',window.location.pathname]);

  typeof loadMeting === 'function' && document.getElementsByClassName('aplayer').length && loadMeting()

  // prismjs
  typeof Prism === 'object' && Prism.highlightAll()

  typeof preloader === 'object' && preloader.endLoading()
})

document.addEventListener('pjax:error', (e) => {
  if (e.request.status === 404) {
    pjax.loadUrl('/404.html')
  }
})</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>